package bma.common.langutil.jmshell.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import bma.common.langutil.core.ClassUtil;
import bma.common.langutil.core.StringUtil;
import bma.common.langutil.core.ValueUtil;
import bma.common.langutil.jmshell.JMShellProcessor;
import bma.common.langutil.jmshell.JMShellUtil;
import bma.common.langutil.jmshell.Session;
import bma.common.langutil.jmshell.cmdopt.CommandOptionParser;
import bma.common.langutil.jmshell.command.CfgCommand;
import bma.common.langutil.jmshell.command.CommandSet;
import bma.common.langutil.jmshell.command.ExecCommand;
import bma.common.langutil.jmshell.command.HistoryCommand;
import bma.common.langutil.jmshell.command.JMShellModule;
import bma.common.langutil.jmshell.command.ListCommand;
import bma.common.langutil.jmshell.command.WatchCommand;
import bma.common.langutil.jmshell.common.JMShellProcessor4Link;
import bma.common.langutil.jmshell.common.JMShellProcessor4Miss;
import bma.common.langutil.jmshell.common.JMShellProcessor4SessionClose;
import bma.common.langutil.jmshell.common.SessionWrap;

public class JMShell implements JMShellProcessor, CommandSet, InitializingBean,
		DisposableBean, ApplicationContextAware {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(JMShell.class);

	protected JMShellProcessor entryProcessor;
	protected JMShellProcessor4Link preset = new JMShellProcessor4Link();
	protected JMShellProcessor filterProcessor;
	protected JMShellModule moduleProcessor;
	protected JMShellProcessor finalProcessor;
	protected JMShellProcessor4Miss miss = new JMShellProcessor4Miss();

	protected Map<String, JMShellProcessor> modules;
	protected Map<String, JMShellProcessor> dynamicModules = Collections
			.synchronizedMap(new HashMap<String, JMShellProcessor>());

	public JMShell() {
		super();

		preset.addProcessor(new JMShellProcessor4SessionClose());
		preset.addProcessor(new CfgCommand());
		preset.addProcessor(new HistoryCommand());
		preset.addProcessor(new ExecCommand());
		preset.addProcessor(new WatchCommand());

		ListCommand lcmd = new ListCommand(this);
		lcmd.addPreset("exit");
		preset.addProcessor(lcmd);
	}

	public void setEntryProcessor(JMShellProcessor entryProcessor) {
		this.entryProcessor = entryProcessor;
	}

	public void setFilterProcessor(JMShellProcessor filterProcessor) {
		this.filterProcessor = filterProcessor;
	}

	public void setFinalProcessor(JMShellProcessor finalProcessor) {
		this.finalProcessor = finalProcessor;
	}

	public void setModules(Map<String, JMShellProcessor> modules) {
		this.modules = modules;
	}

	@Override
	public boolean process(Session session, String command) {
		Session s = new SessionWrap(session) {
			@Override
			public void execute(String command) {
				process(this, command, true);
			}
		};
		return process(s, command, false);
	}

	public boolean process(Session session, String command, boolean subExecute) {
		if (ValueUtil.empty(command))
			return true;
		if (entryProcessor != null) {
			if (entryProcessor.process(session, command)) {
				return true;
			}
		}
		command = HistoryCommand.makeCommand(session, command);
		if (!subExecute) {
			HistoryCommand.pushCommand(session, command);
		}

		command = CfgCommand.makeCommand(session, command);

		if (preset.process(session, command)) {
			return true;
		}
		if (filterProcessor != null) {
			if (filterProcessor.process(session, command)) {
				return true;
			}
		}
		if (moduleProcessor != null) {
			if (moduleProcessor.process(session, command)) {
				return true;
			}
		}
		if (finalProcessor != null) {
			if (finalProcessor.process(session, command)) {
				return true;
			}
		}
		return miss.process(session, command);
	}

	@Override
	public void listCommands(Session session, List<String> results) {
		JMShellUtil.handleListCommand(session, results, entryProcessor);
		JMShellUtil.handleListCommand(session, results, preset);
		JMShellUtil.handleListCommand(session, results, filterProcessor);
		JMShellUtil.handleListCommand(session, results, moduleProcessor);
		JMShellUtil.handleListCommand(session, results, finalProcessor);
	}

	protected class MyShellModule extends JMShellModule {

		protected class MyModuleCommand extends ModuleCommand {

			@Override
			public CommandOptionParser createCommandOption(Session session) {
				CommandOptionParser r = super.createCommandOption(session);
				r.addStringOption((char) 0, "install",
						"install dynamic JMShellModule");
				r.addStringOption((char) 0, "uninstall",
						"uninstall dynamic JMShellModule");
				return r;
			}

			@Override
			public boolean processCommand(Session session, String cmd,
					CommandOptionParser options) {
				String inst = options.getOptionValue("install", "");
				if (inst.length() > 0) {
					installModules(session, inst);
					return true;
				}
				String uninst = options.getOptionValue("uninstall", "");
				if (uninst.length() > 0) {
					uninstallModules(session, uninst);
					return true;
				}
				return super.processCommand(session, cmd, options);
			}

		}

		public MyShellModule() {
			super();
			this.moduleCommand = new MyModuleCommand();
		}

	}

	protected final static String MODULE_DELIM = " ,;\n";

	public void installModules(Session s, String moduleNames) {
		String[] ns = StringUtil.tokenSplit(moduleNames, MODULE_DELIM);
		if (ns != null) {
			for (String n : ns) {
				String clsName = null;
				String[] nc = StringUtil.tokenSplit(n, ":");
				if (nc.length > 1) {
					n = nc[0];
					clsName = nc[1];
				}
				installModule(s, n, clsName);
			}
		}
	}

	protected void installModule(Session s, String n, String clsName) {
		if ((modules != null && modules.containsKey(n))
				|| dynamicModules.containsKey(n)) {
			if (s != null) {
				s.writeln("ERROR: module '" + n + "' already installed");
			}
			return;
		}
		s.writeln("installing module '" + n + "' - '"
				+ (clsName == null ? "" : clsName) + "'");
		Object obj = null;
		if (ValueUtil.notEmpty(clsName)) {
			try {
				Class<?> cls = ClassUtil.forName(clsName);
				obj = cls.newInstance();
			} catch (Exception err) {
				if (s != null) {
					s.writeln("ERROR: module '" + n + "' newInstance '"
							+ clsName + "' fail: " + err.getMessage());
				}
				return;
			}
		} else if (context != null) {
			try {
				obj = context.getBean(n);
			} catch (Exception err) {
				if (s != null) {
					s.writeln("ERROR: module '" + n + "' getBean fail: "
							+ err.getMessage());
				}
				return;
			}
		}
		if (obj == null) {
			if (s != null) {
				s.writeln("ERROR: can't find object for module '" + n + "'");
			}
			return;
		}
		JMShellProcessor p = null;
		if (obj instanceof JMShellProcessor) {
			p = (JMShellProcessor) obj;
		} else if (obj instanceof JMShellProcessorFactory) {
			JMShellProcessorFactory fac = (JMShellProcessorFactory) obj;
			p = createFromFactory(s, n, fac, null);
			if (p == null)
				return;
		} else {
			String cname = obj.getClass().getName()
					+ "_JMShellProcessorFactory";
			Object o2 = null;
			try {
				Class<?> cls = ClassUtil.forName(cname);
				o2 = cls.newInstance();
			} catch (Exception err1) {
			}
			try {
				if (o2 != null && o2 instanceof JMShellProcessorFactory) {
					JMShellProcessorFactory fac = (JMShellProcessorFactory) o2;
					p = createFromFactory(s, n, fac, obj);
					if (p == null)
						return;
				}
			} catch (Exception err2) {
				if (log.isDebugEnabled()) {
					log.debug("try create JMShellProcessor fail", err2);
				}
			}
			if (p == null) {
				if (s != null) {
					s.writeln("ERROR: module '" + n
							+ "' not a JMShellProcessor");
				}
				return;
			}
		}

		dynamicModules.put(n, p);
		moduleProcessor.getProcessors().put(n, p);

		if (p instanceof JMShellAware) {
			JMShellAware o = (JMShellAware) p;
			try {
				o.onInstall(this, n);
			} catch (Exception err) {
				log.warn("install '" + n + "," + p + "' notify fail", err);
			}
		}

		if (s != null) {
			s.writeln("install module '" + n + "' done");
		}
	}

	protected JMShellProcessor createFromFactory(Session s, String n,
			JMShellProcessorFactory fac, Object obj) {
		try {
			return fac.createProcessor(s, obj);
		} catch (Exception err) {
			if (s != null) {
				s.writeln("ERROR: module '" + n
						+ "' factory create processor fail: "
						+ err.getMessage());
			}
			return null;
		}
	}

	public void uninstallModules(Session s, String moduleNames) {
		String[] ns = StringUtil.tokenSplit(moduleNames, MODULE_DELIM);
		if (ns != null) {
			for (String n : ns) {
				uninstallModule(s, n);
			}
		}
	}

	protected void uninstallModule(Session s, String n) {
		JMShellProcessor p = dynamicModules.get(n);
		if (p == null) {
			if (s != null) {
				s.writeln("ERROR: module '" + n + "' not install");
			}
			return;
		}

		dynamicModules.remove(n);
		moduleProcessor.getProcessors().remove(n);

		if (p instanceof JMShellAware) {
			JMShellAware o = (JMShellAware) p;
			try {
				o.onUninstall(this, n);
			} catch (Exception err) {
				log.warn("uninstall '" + n + "," + p + "' notify fail", err);
			}
		}

		if (s != null) {
			s.writeln("uninstall module '" + n + "' done");
		}
	}

	public void init() {
		moduleProcessor = new MyShellModule();
		Map<String, JMShellProcessor> ps = new ConcurrentHashMap<String, JMShellProcessor>();
		if (modules != null) {
			ps.putAll(modules);
		}
		moduleProcessor.setProcessors(ps);
	}

	public void close() {
		Map<String, JMShellProcessor> tmp = new HashMap<String, JMShellProcessor>(
				dynamicModules);
		for (String name : tmp.keySet()) {
			uninstallModule(null, name);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		init();
	}

	@Override
	public void destroy() throws Exception {
		close();
	}

	protected ApplicationContext context;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		context = applicationContext;
	}
}
